#include <cstdio>
#include <cstring>
#include <cassert>

#include <iostream>

#include <QWidget>
#include <QMouseEvent>
#include <QFileDialog>
#include <QCoreApplication>


#include <opencv2/highgui/highgui.hpp>
using namespace cv;

#include "remoteimageview.h"


using namespace std;

#define DEBUG   0
static inline void setPosRangeScale(int &v, int max, int ori_max){
    if(v < 0) v = 0;
    else if( v > max) v = max;
    v = v * ori_max / max;
}

static void adjustPoints(Point2i &pt1, Point2i &pt2, int width, int height, int ori_width, int ori_height){
    int x1, x2, y1, y2;
    x1 = pt1.x;
    x2 = pt2.x;
    y1 = pt1.y;
    y2 = pt2.y;
    setPosRangeScale(x1, width, ori_width);
    setPosRangeScale(x2, width, ori_width);
    setPosRangeScale(y1, height, ori_height);
    setPosRangeScale(y2, height, ori_height);
    pt1.x = x1 < x2 ? x1: x2;
    pt1.y = y1 < y2 ? y1: y2;
    pt2.x = x2 > x1 ? x2: x1;
    pt2.y = y2 > y1 ? y2: y1;
}

RemoteImageView::RemoteImageView(QWidget *parent) :
    ImageView(parent)
{
    QObject::connect(&this->m_transfer, SIGNAL(gotResponse(QByteArray)), this, SLOT(recvResponse(QByteArray)));
    QObject::connect(&this->m_transfer, SIGNAL(gotImageData(QByteArray)), this, SLOT(recvImageData(QByteArray)));
    QObject::connect(this, SIGNAL(setTransferServer(QHostAddress,quint16,quint16,quint16)), &this->m_transfer, SLOT(setServer(QHostAddress,quint16,quint16,quint16)));
    QObject::connect(this, SIGNAL(imgRequest(uint,int,int,int,int,int)), &this->m_transfer, SLOT(imgRequest(uint,int,int,int,int,int)));
    QObject::connect(this, SIGNAL(ptzRequest(int,int,int)), &this->m_transfer, SLOT(ptzRequest(int,int,int)));
    QObject::connect(this, SIGNAL(trackRequest(int,int,int,int,int)), &this->m_transfer, SLOT(trackRequest(int,int,int,int,int)));
    QObject::connect(&this->m_req_timer, SIGNAL(timeout()), this, SLOT(reqestTimerAction()));
    QObject::connect(&this->m_stat_timer, SIGNAL(timeout()), this, SLOT(statisticsTimer()));
    QObject::connect(&this->m_sync_timer, SIGNAL(timeout()), this, SLOT(syncTimerAction()));
    QObject::connect(this, SIGNAL(changeCamera(int,int,int,int,int)), &this->m_transfer, SLOT(cameraRequest(int,int,int,int,int)));

    this->m_transfer_state = false;
    this->m_req_timer.setInterval(250);
    this->m_req_timer.start();


    this->m_stat_timer.setInterval(STATISTICS_INTERVAL);
    this->m_stat_timer.start();

    //for display mutex
    this->m_display_new = true;
    this->m_mouse_pressed = false;
    this->m_show_selected = false;


    //for address
    this->m_addr_str = "127.0.0.1";
//    this->m_addr.setAddress("127.0.0.1");
//    emit setTransferServer(this->m_addr, DEFAULT_SERVER_SYNC_PORT, DEFAULT_SERVER_INFO_PORT, DEFAULT_SERVER_DATA_PORT);

    //for save file
    this->m_save_folder = QCoreApplication::applicationDirPath();
    emit showSaveFolder(QCoreApplication::applicationDirPath());
    this->m_save_state = false;

    //for statistics
    this->m_frame_cost = -1;
    this->m_frame_count = 0;

    //for sync
    this->m_has_connection = false;
    this->m_sync_timer.setInterval(200);
    this->m_sync_timer.start();

    //for camera
    this->m_camera_brightness = 50;
    this->m_camera_contrast = 0;
    this->m_camera_hue = 50;
    this->m_camara_saturation = 0;

    //for image
    this->m_last_img_ts = (unsigned int)-1;
}

void RemoteImageView::statisticsTimer(){
    emit frameRate(this->m_frame_count * (1000 / STATISTICS_INTERVAL));
    m_frame_count = 0;
}

void RemoteImageView::reqestTimerAction(){

        if(this->m_transfer_state){


//            emit imgRequest((int)this->m_img_ts.toInt(), this->m_img_seq, this->m_ori_width, this->m_ori_height, this->m_img_once_count);
        }

}

void RemoteImageView::syncTimerAction(){
    if(this->m_has_connection){
        int delay = TimeStamp::now()->diff(this->m_last_sync_ts);
        if(delay >= 5000){
            emit syncDelay(-1);
        }else if(delay >= 1000){
            emit syncDelay(delay);
        }
    }
}

void RemoteImageView::changeTargetDirection(int x, int y, int z){
    x = (x+360) % 360;
    y = (y+360) % 360;
    z = (z+360) % 360;
    emit ptzRequest(x, y, z);
}

void RemoteImageView::transferState(int s){

    if (s > 0){
        this->m_transfer_state = true;
    }else{
        this->m_transfer_state = false;
    }
    emit imgRequest((unsigned int)-1, -1, -1, -1, this->m_transfer_state?1:0, -1);
}

bool RemoteImageView::isOld(unsigned int ts){
    if(this->m_last_img_ts == (unsigned int) -1){
        this->m_last_img_ts = ts;
        return false;
    }else{
        int d = this->m_last_img_ts.diff(ts);
        if(d < 43200000){
            this->m_last_img_ts = ts;
            return true;
        }else{
            return false;
        }
    }
}

void RemoteImageView::restructImg(Mat &img, unsigned int ts){
    vector<unsigned char> buf;
    this->m_img_info_mutex.lock();
    image_transfer_info_t info;
    assert(this->m_img_info.find(ts, &info));
    this->m_img_info_mutex.unlock();
    buf.resize(info.size);
    this->m_img_data_mutex.lock();
    assert(m_img_data.getCount(info.timestamp) == info.total_seq);
    for (int i = 0; i < info.total_seq; ++i){
        m_img_data.getData(ts, i, &buf[0], i * RESPONSE_DATA_LEN);
    }
    this->m_img_data_mutex.unlock();
    img = imdecode(buf, 1);
}



void RemoteImageView::recvResponse(QByteArray response){
    normal_response_t* ans = (normal_response_t*) response.data();
    if(requestInfo(ans)->magic != RESPONSE_MAGIC)//check magic
        return;

    switch(requestInfo(ans)->type){
    case SYNC_RESPONSE:
    {
        TimeStamp last((unsigned int)getResponseField(ans, SYNC_TIME));
        int _delay = TimeStamp::now()->diff(last);
        this->m_last_sync_ts = TimeStamp();
        emit syncDelay(_delay);

    }
        break;
    case IMAGE_RESPONSE:
    {
        unsigned int timestamp = (unsigned int)getResponseField(ans, IMAGE_TIMESTAMP);
        int total_seq = getResponseField(ans, IMAGE_SEQ);
        int size = getResponseField(ans, IMAGE_SIZE);
        int width = getResponseField(ans, IMAGE_WIDTH);
        int height = getResponseField(ans, IMAGE_HEIGHT);
        int tran_switch = getResponseField(ans, IMAGE_TRAN_SWITCH);
        int tran_speed = getResponseField(ans, IMAGE_TRAN_SPEED);
        image_transfer_info_t info;
        info.timestamp = timestamp;
        info.size = size;
        info.total_seq = total_seq;
        info.expect_seq = 0;
        this->m_img_info_mutex.lock();
        this->m_img_info.enqueue(timestamp, info);
        this->m_img_info_mutex.unlock();
        emit changeImageTranSwitch(tran_switch - 1);
        emit changeImageTranSpeed(tran_speed);
        if(this->m_transfer_state)
            emit imgRequest(timestamp, -1, -1, -1, -1, -1);
#if DEBUG
                qDebug()<<"ts = "<<timestamp
                <<" size = "<<size
                <<" total_seq = "<<total_seq
                <<" width = "<<width
                <<" height = "<<height
                <<" switch = "<<tran_switch
                <<" speed = "<<tran_speed;
#endif
    }
        break;
    case PTZ_RESPONSE:
    {
        int x = getResponseField(ans, PTZ_X);
        int y = getResponseField(ans, PTZ_Y);
        int z = getResponseField(ans, PTZ_Z);
        if(x > 180) x -= 360;
        if(y > 180) y -= 360;
        if(z > 180) z -= 360;
        emit changeDirection(x, y, z);
        emit changePtzX(x);
        emit changePtzY(y);
        emit changePtzZ(z);
    }
        break;
    case CAMERA_RESPONSE:
    {
        int brightness, contrast, hue, saturation, record;
        brightness = getResponseField(ans, CAMERA_BRIGHTNESS);
        contrast = getResponseField(ans, CAMERA_CONTRAST);
        hue = getResponseField(ans, CAMERA_HUE);
        saturation = getResponseField(ans, CAMERA_SATURATION);
        record = getResponseField(ans, CAMERA_RECORD);
        emit changeBrightness(brightness);
        emit changeContrast(contrast);
        emit changeHue(hue);
        emit changeSaturation(saturation);
        emit changeCameraRecord(record - 1);
        int count = 0;
        if(brightness != this->m_camera_brightness)
            ++count;
        if(contrast != this->m_camera_contrast)
            ++count;
        if(hue != this->m_camera_hue)
            ++count;
        if(saturation != this->m_camara_saturation)
            ++count;
        if(count >=2){
            this->m_camera_brightness = brightness;
            this->m_camera_contrast = contrast;
            this->m_camera_hue = hue;
            this->m_camara_saturation = saturation;
            emit syncBrightness(this->m_camera_brightness);
            emit syncContrast(this->m_camera_contrast);
            emit syncHue(this->m_camera_hue);
            emit syncSaturation(this->m_camara_saturation);
        }
    }
        break;
    case TRACK_RESPONSE:
    {

        this->m_track_ans_pt1 = Point2i(getResponseField(ans, TRACK_PT_X1), getResponseField(ans, TRACK_PT_Y1));
        this->m_track_ans_pt2 = Point2i(getResponseField(ans, TRACK_PT_X2), getResponseField(ans, TRACK_PT_Y2));
        this->m_track_center = Point2i(getResponseField(ans, TRACK_PT_CENTER_X), getResponseField(ans, TRACK_PT_CENTER_Y));
        adjustPoints(this->m_track_ans_pt1, this->m_track_ans_pt2, this->m_ori_width, this->m_ori_height, this->width(), this->height());
        this->m_track_ts.set((unsigned int)getResponseField(ans, TRACK_TIMESTAMP));
    }
        break;
    default:
        break;
    }


//    qDebug()<<TimeStamp::now()->toInt()<<" response ts = "<< ans->img_timestamp<<" size = "<<ans->img_size<<" has image ="<<ans->has_img;

}



void RemoteImageView::recvImageData(QByteArray image_data){
    response_data_t* ans = (response_data_t*) image_data.data();

    if(responseInfo(ans)->type != IMAGE_RESPONSE){
        qDebug()<<"do not accept other response!";
        return;
    }

    unsigned int timestamp = (unsigned int) getResponseDataInfo(ans, IMAGE_TIMESTAMP);
    int size = getResponseField(ans, IMAGE_SIZE);
    int seq = getResponseDataInfo(ans, IMAGE_SEQ);
    int seq_size = getResponseDataInfo(ans, IMAGE_SEQ_SIZE);
    if(isOld(timestamp)){
        return;
    }
    image_transfer_info_t info;
    this->m_img_info_mutex.lock();
    bool correct_img = this->m_img_info.find(timestamp, &info);
    this->m_img_info_mutex.unlock();
    if(correct_img){
        assert(info.size == size);
        this->m_img_data_mutex.lock();
        this->m_img_data.cacheData(timestamp, seq, responseDataPtr(ans), seq_size);
        this->m_img_data_mutex.unlock();
        if(seq == info.expect_seq){
            this->m_img_data_mutex.lock();
            for (int i = info.expect_seq + 1; i <= info.total_seq; ++i){
                if(!this->m_img_data.has(timestamp, i)){
                    info.expect_seq = i;
                    break;
                }
            }
            this->m_img_data_mutex.unlock();
            if (info.expect_seq == info.total_seq){
                Mat img;
                restructImg(img, timestamp);
                saveImg2Video(img);
                this->m_display_mutex.lock();
                this->showImage(img);
                ++this->m_frame_count;
                this->m_frame_cost = TimeStamp::now()->diff(this->m_frame_ts);
                this->m_frame_ts.setNow();
                this->m_display_mutex.unlock();
            }
            emit imageTransferProgress(info.expect_seq * 100 / info.total_seq);
        }else{
            emit imgRequest(timestamp, info.expect_seq, -1, -1, -1, -1);
        }
        this->m_img_info_mutex.lock();
        this->m_img_info.set(timestamp, info);
        this->m_img_info_mutex.unlock();
#if DEBUG
        qDebug()<<"recv"<<seq<<"expect"<<info.expect_seq<<this->m_img_data.getCount(timestamp)<<"total"<<info.total_seq;
#endif
    }


}


void RemoteImageView::mousePressEvent(QMouseEvent *event){
    if(event->button() == Qt::LeftButton){
        this->m_display_mutex.lock();
        this->m_mouse_pressed = true;
        this->m_show_selected = true;
//        this->m_display_new = false;
        this->m_display_mutex.unlock();
//        qDebug()<<"pressed"<<event->pos();
        this->m_track_pt1.x = event->x();
        this->m_track_pt1.y = event->y();
    }
}



void RemoteImageView::mouseReleaseEvent(QMouseEvent *event){
    if(event->button() == Qt::LeftButton){
        this->m_display_mutex.lock();
        this->m_mouse_pressed = false;
        this->m_show_selected = false;
        this->m_display_mutex.unlock();
        this->m_track_pt2.x = event->x();
        this->m_track_pt2.y = event->y();
        adjustPoints(this->m_track_pt1,
                     this->m_track_pt2,
                     this->width(),
                     this->height(),
                     this->m_ori_width,
                     this->m_ori_height);
        emit trackRequest((int)this->m_display_ts.toInt(),
                          this->m_track_pt1.x,
                          this->m_track_pt1.y,
                          this->m_track_pt2.x,
                          this->m_track_pt2.y);

    }
}

void RemoteImageView::mouseMoveEvent(QMouseEvent *event){
    this->m_display_mutex.lock();
    if(this->m_mouse_pressed){
        this->m_track_pt2.x = event->x();
        this->m_track_pt2.y = event->y();
        this->showSelectedRect(this->m_track_pt1,this->m_track_pt2, Scalar(255, 0, 0));
    }
    this->m_display_mutex.unlock();
}

void RemoteImageView::setAddressString(QString addr){
    this->m_addr_str = addr;
#if DEBUG
    qDebug()<<addr;
#endif
}

void RemoteImageView::setAddress(){
    QHostInfo host_info = QHostInfo::fromName(this->m_addr_str);
    this->m_addr = host_info.addresses().first();
    emit setTransferServer(this->m_addr, DEFAULT_SERVER_SYNC_PORT, DEFAULT_SERVER_INFO_PORT, DEFAULT_SERVER_DATA_PORT);
    this->m_has_connection = true;
}

void RemoteImageView::setSaveFolder(){
    this->m_save_folder = QFileDialog::getExistingDirectory(0, QString(), this->m_save_folder);
    emit showSaveFolder(this->m_save_folder);
}

void RemoteImageView::setSaveState(int s){
    this->m_save_mutex.lock();
    if(s > 0){
        this->m_save_state = true;
        changeCamera(-1, -1, -1, -1, 1);
    }else{
        this->m_save_state = false;
        this->stopSaveVideo();
        changeCamera(-1, -1, -1, -1, 0);
    }
    this->m_save_mutex.unlock();
}

void RemoteImageView::startSaveVideo(){
//    if(!this->m_img.empty()){
//        string filename = this->m_save_folder.toStdString() + "/" + this->m_img_ts.toFileName() + ".avi";
//        m_save_writer = VideoWriter(filename,
//                                    CV_FOURCC('X','V','I','D'),
//                                    30,
//                                    Size(this->m_img.cols, this->m_img.rows),
//                                    true);
//    }else{
//        qDebug()<<"no image now, wait the first image to record";
//    }
}

void RemoteImageView::stopSaveVideo(){
    if(this->m_save_writer.isOpened()){
        this->m_save_writer = VideoWriter();
    }
}

void RemoteImageView::saveImg2Video(Mat img){
    this->m_save_mutex.lock();
    if(this->m_save_state){
        if(!this->m_save_writer.isOpened()){
            string filename = this->m_save_folder.toStdString() + "/" + TimeStamp::now()->toFileName() + ".avi";
            this->m_save_writer.open(filename,
                                     CV_FOURCC('X','V','I','D'),
                                     30,
                                     img.size(),
                                     true);
        }
        this->m_save_writer<<img;
    }
    this->m_save_mutex.unlock();
}

void RemoteImageView::setBrightness(int b){
    this->m_camera_brightness = b;
}

void RemoteImageView::setContrast(int c){
    this->m_camera_contrast = c;

}

void RemoteImageView::setHue(int h){
    this->m_camera_hue = h;

}

void RemoteImageView::setSaturation(int s){
    this->m_camara_saturation = s;
}

void RemoteImageView::setCameraProp(){
    emit changeCamera(this->m_camera_brightness, this->m_camera_contrast, this->m_camera_hue, this->m_camara_saturation,  -1);
}

void RemoteImageView::setImageTranSpeed(int s){
    emit imgRequest((unsigned int)-1, -1, -1, -1, -1, s);
}
